#!/bin/bash
### BEGIN INIT INFO
# Provides:          realtime
# Required-Start:    $remote_fs $syslog
# Required-Stop:     $remote_fs $syslog
# Default-Start:
# Default-Stop:      0 1 6
# Short-Description: enable realtime capabilities when needed
# Description:       The rtai module is loaded when needed and
#  removed when the job has ended and is used throughout the LinuxCNC
#  infrastructure. The script has no effect for the PREEMPT realtime kernel.
#  The script is not meant to be started a boot time - it could be, though.
# X-Interactive:     false
### END INIT INFO
#
# @configure_input@ 
# on @DATE@
#

export LANG=C

GREP=@GREP@
PS=@PS@

CheckKernel() {
    # Shellcheck doesn't know about substitution
    # shellcheck disable=SC2194
    case "@KERNEL_VERS@" in
    "") ;;
    *)
	if [ "$(uname -r)" != "@KERNEL_VERS@" ]; then
	    cat 1>&2 << EOF
RTAPI requires the real-time kernel @KERNEL_VERS@ to run.  Before running
this realtime application, reboot and choose this kernel at the boot menu.
EOF
	    exit 1
	fi
    esac
}

RUN_IN_PLACE=@RUN_IN_PLACE@
if [ $RUN_IN_PLACE = yes ]; then
    # When using RIP make sure PATH includes the directory where rtapi_app
    # resides
    PATH=@EMC2_HOME@/bin:$PATH; export PATH
fi

CheckConfig(){
    prefix=@prefix@
    exec_prefix=@exec_prefix@
    sysconfdir=@sysconfdir@
    if [ "$RUN_IN_PLACE" = "yes" ]; then
        RTAPICONF=
        # check in the LinuxCNC scripts directory
        # $0 is the command to run this script
        # strip the script name to leave the path
        SCRIPT_DIR=${0%/*}
        # the path might be relative
        # convert it to an absolute path
        SCRIPT_DIR=$(realpath "$SCRIPT_DIR")
        # now look for rtapi.conf there
        if [ -f "$SCRIPT_DIR/rtapi.conf" ] ; then
            RTAPICONF=$SCRIPT_DIR/rtapi.conf
        fi
    else
        if [ -f "$sysconfdir/linuxcnc/rtapi.conf" ]; then
                RTAPICONF=$sysconfdir/linuxcnc/rtapi.conf
        fi
    fi
    if [ -z "$RTAPICONF" ] ; then
	echo "Missing rtapi.conf.  Check your installation." 1>&2
	exit 1
    fi
    INSMOD="@INSMOD@"
    RMMOD="@RMMOD@"
    LSMOD="@LSMOD@"
    FUSER="@FUSER@"

    # Import the config
    source "$RTAPICONF"
    if [ ! -e "$RTLIB_DIR/.runinfo" ] && [ -e "$RTLIB_DIR/emc2/.runinfo" ]; then
        # installed system: we don't want our modules mixed up with rtai
        RTLIB_DIR=$RTLIB_DIR/emc2
    fi
    # Export the module path specified in the config.
    export MODPATH
    # Generate module lists for loading and unloading
    # lists contain RTOS modules plus RTAPI and HAL
    # unload list is in reverse order
    # any module names that are symlinks are resolved to their real names
    MODULES_LOAD=
    MODULES_UNLOAD=
    case $RTPREFIX in
    uspace) SHM_DEV=/dev/zero;;
    esac
    for  MOD in $MODULES ; do
        eval MOD=\${MODPATH_"$MOD"}
        if [ -z "$MOD" ]; then continue; fi
        if [ -L "$MOD" ]; then
            MOD=${MOD%/*}/$(readlink "$MOD")
        fi
        MODULES_LOAD="$MODULES_LOAD $MOD"
        MOD="${MOD##*/}"
        MOD="${MOD%.*}"
        MODULES_UNLOAD="$MOD $MODULES_UNLOAD"
    done
    case $RTPREFIX in
    (*rtai*)
        MODULES_LOAD="$MODULES_LOAD $RTLIB_DIR/rtapi$MODULE_EXT $RTLIB_DIR/hal_lib$MODULE_EXT"
        MODULES_UNLOAD="hal_lib rtapi $MODULES_UNLOAD"
        SHM_DEV=/dev/rtai_shm
	;;
    (*)
        MODULES_UNLOAD=
    esac
}

CheckStatus(){
    case $RTPREFIX in
    uspace)
        if [ -z "$($PS -o stat= -o comm= -C rtapi_app | $GREP -v '^Z')" ]; then
            exit 1
        else
            exit 0
        fi ;;
    *)
        # check loaded/unloaded status of modules
        unset NOTLOADED
        for MOD in $MODULES_UNLOAD ; do
            if "$LSMOD" | awk '{print $1}' | $GREP -x "$MOD" >/dev/null ; then
                echo "$MOD is loaded"
            else
                echo "$MOD is not loaded"
                NOTLOADED=NOT
            fi
        done
        if [ -z "$NOTLOADED" ]; then
            exit 0
        else
            exit 1
        fi
    esac
}

CheckMem(){
# check for user space processes using shared memory
    if [ -e /dev/mbuff ] ; then
	# device file exists, check for processes using it
	if $FUSER -s /dev/mbuff 2>/dev/null; then
	    # at least one process is using it
	    echo "ERROR:  Can't remove RTLinux modules, kill the following process(es) first"
	    $FUSER -v /dev/mbuff
	    exit 1
	fi
    elif [ -e /dev/rtai_shm ] ; then
	# device file exists, check for processes using it
	if $FUSER -s /dev/rtai_shm 2>/dev/null; then
	    # at least one process is using it
	    echo "ERROR:  Can't remove RTAI modules, kill the following process(es) first"
	    $FUSER -v /dev/rtai_shm
	    exit 1
	fi
    fi
}

Load(){
    CheckKernel
    for MOD in $MODULES_LOAD ; do
	if ! [ -d "/sys/module/$(basename "$MOD" .ko)" ]; then
		$INSMOD "$MOD" || return $?
	fi
    done
    if [ "$DEBUG" != "" ] && [ -w /proc/rtapi/debug ] ; then
        echo "$DEBUG" > /proc/rtapi/debug
    fi
}

CheckLoaded(){
    # this abomination is needed because udev sometimes doesn't
    # have the device ready for us in time.
    n=0
    while [ $n -lt 100 ]; do
        [ -w "$SHM_DEV" ] && return 0
        echo "." 1>&2
        sleep .1
        n=$((n + 1))
    done
    echo "Can't write to $SHM_DEV - aborting" 1>&2
    exit 1
}

Unload(){
    CheckKernel
    case $RTPREFIX in
    uspace)
        rtapi_app exit 

        # wait 5 seconds for rtapi_app to die and be reaped by its parent
        START=$SECONDS
        local NPROCS
        while [ 5 -gt $((SECONDS-START)) ]; do
            NPROCS=$(ps -C rtapi_app -o stat= -o comm= | $GREP -v '^Z' | wc -l)
            if [ "$NPROCS" -eq 0 ]; then
                break
            fi
            sleep 0.1
        done
        NPROCS=$(ps -C rtapi_app -o stat= -o comm= | $GREP -v '^Z' | wc -l)
        if [ "$NPROCS" -gt 0 ]; then
            echo "ERROR: rtapi_app failed to die" 1>&2
        fi

        ipcrm -M 0x48414c32 2>/dev/null ;# HAL_KEY
        ipcrm -M 0x90280A48 2>/dev/null ;# RTAPI_KEY
        ipcrm -M 0x48484c34 2>/dev/null ;# UUID_KEY
    esac
    for module in $MODULES_UNLOAD ; do
        $RMMOD "$module"
    done
}

CheckUnloaded(){
# checks to see if all modules were unloaded
    STATUS=
    for module in $MODULES_UNLOAD ; do
	# check to see if the module is installed
	if "$LSMOD" | awk '{print $1}' | $GREP -x "$module" >/dev/null ; then
	    echo "ERROR: Could not unload '$module'"
	    STATUS=error
	fi
    done
    if [ -n "$STATUS" ] ; then
	exit 1
    fi
}

CMD=$1

case "$CMD" in
  start|load)
	CheckConfig
	Load || exit $?
	CheckLoaded
	;;
  restart|force-reload)
	CheckConfig
	CheckMem
	Unload
	CheckUnloaded
	Load || exit $?
	CheckLoaded
	;;
  stop|unload)
	CheckConfig
	CheckMem
	Unload || exit $?
	;;
  status)
	CheckConfig
	CheckStatus
	;;
  *)
	echo "Usage: $0 {start|load|stop|unload|restart|force-reload|status}" >&2
	exit 1
	;;
esac

exit 0

